<?php
/**
 * 基于左右分值算法的无限分类模型
 * 节点的键：id,lft,rgt,ply,name，若无特别说明，各方法中为Node型的参数均需包含此三个键值
 * 在建立一棵新树的时候，需建立其根节点,即写入一各初始记录(id=1,lft=1,rgt=2.ply=1)
 */

class TaxonomyModel extends Model {

    /**
     * 添加节点
     * @param array $insertNode 新节点,无需指定id,rgt,lft,ply值
     * @param array $goalNode 新节点的父亲节点
     * @param array $leftNode 新节点的左邻居，用于指定新节点的位置。如果不指定此位置，则新节点将成为goalNode的最后一个子节点
     * @return boolean true表明添加成功，反之失败
     */
    public function insertNode($insertNode,$goalNode,$leftNode=null){
        if(empty($leftNode)){
            $leftNode['lft']=$leftNode['rgt']=intval($goalNode['rgt'])-1;
            $leftNode['ply']=intval($goalNode['ply'])+1;
        }
        $this->startTrans();
        $sqls=array();
        $sqls[]="update __TABLE__ set lft=lft+2 where lft>{$leftNode['lft']};";
        $sqls[]="update __TABLE__ set rgt=rgt+2 where rgt>{$leftNode['rgt']};";
        if(false===$this->executeBatch($sqls) ){
            $this->rollback();
            return false;
        }
        $insertNode['lft']=intval($leftNode['lft'])+1;
        $insertNode['rgt']=$insertNode['lft']+1;
        $insertNode['ply']=$leftNode['ply'];
        $result=$this->add($insertNode);
        if(false===$result){
            $this->rollback();
            return false;
        }
        $this->commit();
        return $result;
    }

    /**
     * 检查某父节点的下一层节点是是否存在同属性节点
     */
    protected function checkNodeExistedHierarchy($parentNode,$checkNode,$checkField='name'){
        $cond=' ply='.(intval($parentNode['ply'])+1);        //确定层级
        $cond.=' and lft between '.$parentNode['lft'].' and '.$parentNode['rgt'];       //确定子节点范围
        $cond.=' and '.$checkField.'=\''.$checkNode[$checkField].'\'';
        if($checkNode['id']){
            $cond.=' and id <> '.$checkNode['id'];    //如果有ID存在，则要排除当前节点
        }
        if($this->where($cond)->find()){
            return false;
        }
        return true;
    }

    /**
     * 节点移动（类型--消隐）（不知道有没有专有名词）
     *
     * 将某节点（movedNode）移动到其他节点（goalNode）之下，使movedNode成为goalNode的子节点
     *
     * 1、goalNode不是movedNode的子孙节点,goalNode和movedNode也不同父节点
     * 2、movedNode的子孙节点保持原次序不变，成为movedNode原父节点的子孙节点
     * 3、可指定移动到goalNode的具体位置，假定目标位置是处于leftNode和rightNode之间。leftNode和rightNode必有其一，实际处理时只需要rightNode
     * 以上过程类似于俄罗斯方块游戏中的消隐，故称之为消隐移动
     *
     * 算法：
     * 1.goalNode.lef-=2
     * 2.movedNode.allChildren.left|right-=1
     * 3.rightNode的所有左边节点（需是goalNode的子孙节点）的左右值均减2
     * 4.movedNode.left=rightNode.left-2,movedNode.right=rightNode.left-1。当rightNode没有指定时可用goalNode.right代替rightNode.left
     */
    protected function moveWithoutChildren($movedNode,$goalNode,$rightNode=null){
        $tableName=$this->getTableName();
        $this->startTrans();
        //更新movedNode子孙节点的左右值
        $sql1="update {$tableName} set lft=lft-1,rgt=rgt-1 where lft>{$movedNode['lft']} and rgt<{$movedNode['rgt']}";
        //更新goalNode等节点
        if( ! $rightNode ){
            $rightNode['lft']=$goalNode['rgt'];
        }
        $sql2="update {$tableName} set lft=lft-2 where id={$goalNode['id']}";
        $sql3="update {$tableName} set lft=lft-2,rgt=rgt-2 where lft>{$goalNode['lft']} and rgt<{$rightNode['lft']}";
        $sql4="update {$tableName} set lft={$rightNode['lft']}-2,rgt={$rightNode['lft']}-1 where id={$movedNode['id']}";
        if(false!==$this->execute($sql1) && false!==$this->execute($sql2) && false!==$this->execute($sql3) && $this->commit()){
            return true;
        }
        $this->rollback();
        return false;
    }

    /**
     * 和moveWithoutChildren类似，但连带子孙节点一起移动
     */
    public function moveWithChildren(){
    }

    /**
     * 获取树的树状结构
     *
     * filter过滤条件，直接写入where字段
     */
    public function getTree($filter='',$parentNode=null){
        $where = array();
        if($filter) $where[] = $filter;
        if($parentNode) $where[] = 'lft between '.$parentNode['lft'].' and '.$parentNode['rgt'];
        if(count($parentNode)>0) $this->where(implode(' and ',$where));
        $data = $this->order('lft')->findAll();
        $tree = array();
        foreach($data as $row){
            $tree[$row['id']] = $row;
        }
        return $tree;
    }

    /**
     * 批量执行SQL语句
     * @param array $sqls 要执行的SQL语句数组
     * @return boolean 执行结果
     */
    protected function executeBatch($sqls){
        foreach($sqls as $sql){
            if(false===$this->execute($sql)){
                return false;
            }
        }
        return true;
    }
}

?>
